Android — BroadcastReceiver

前言

是时候展现真正的…..哦不,是时候学习一下BroadcastReceiver了。学习Android快半年了,在最开始的讲解四大组件,但是在实践时候也就用到了Activity和Service,Service也很少用。对于BroadcastReceiver和ContentProvider的了解也只限于了解。这次,就全面系统学习一下这个BroadcastReceiver,并实践一下该用到何处。

大致目录结构如下:

  1. 简介

    1.1 广播发送者和广播接收者

    1.2 实现原理

  2. 发送广播

    2.1 自定义BroadcastReceiver

    2.2 BroadcastReceiver的注册类型

    2.3 BroadcastReceiver示例

  3. 有序广播

  4. 接收系统广播消息

emmmm,看完再动手写一下示例,基本上就OK了。

简介

  • 广播发送者和广播接收者

    Android 广播可以分为两个方面:广播发送者和广播接收者。BroadcastReceiver指的就是广播接收者,用于异步接收广播Intent。广播Intent通过调用Context.sendBroadcast()实现发送广播、BroadcastReceiver()方法实现接收广播。通常,一个广播Intent可以被订阅了此Intent的多个广播接收者接收。

    广播接收器只能接收广播,对广播的通知做出响应,很多广播都产生于系统代码,如时区的变化、电池电量不足、用户改变了语言偏好或开机启动等等。

    广播接收器没有用户界面,但是它可以为接收到的信息启动一个Activity或者使用NotificationManager来通知用户。

    BroadcastReceiver接收广播的方式有两种:

    1. Normal Broadcasts(正常广播),调用Context.sendBroadcast()方法发送是完全异步的,它们都运行在一个未定义的顺序下,通常是在同一时间。
    2. Ordered Broadcasts(有序广播),调用Context.sendOrderdBroadcast()方法每次将广播发送到一个Receiver。所谓有序,就是每个Receiver执行后可以传播到下一个Receiver,也可以完全中止传播,即不传播给其他Receiver。而Receiver运行的顺序是可以通过matched intent-filter 里面的android:priority来控制,当priority优先级相同的时候,Receiver以任意顺序执行。
  • 实现原理

从实现的原理上来看,Android中的广播使用了观察者模式,即基于事件的发布/订阅事件模型。因此,从实现角度来看,Android中的广播将广播的发送和接收极大限度的解耦,使得系统能够很方便集成,更易扩展。具体实现流程如下:

  1. 广播接收者BroadcastReceiver通过Binder机制向AMS(Activity Manager Service)进行注册。
  2. 广播发送者通过Binder机制向AMS发送广播。
  3. AMS查找符合相应条件(IntentFilter/Permission等)的BroadcastReceiver,将广播发送到BroadcastReceiver(一般情况下是Activity)响应的消息循环队列中。
  4. 消息循环执行接收到此广播,回调BroadcastReceiver中的onReceive()方法。

    对于不同的广播类型,以及不同的BroadcastReceiver注册方式,具体实现上会有不同,但是总体流程如上。

    由此,广播发送者和广播接收者分别属于观察者模式中的消息发布和订阅两端,AMS属于中间的处理中心。广播发送者和广播接收者的执行是异步的,发出去的广播不会关心有没有接收者,也不确定接收者到底何时才能接受到广播。

发送广播

  • 自定义BroadcastReceiver

    自定义广播接收器需要继承基类BroadcastReceiver,并实现抽象方法onReceive(context,intent)方法。广播接收器接受到相应广播后,会自动调用onReceive()方法。默认情况下,广播接收器也是运行在UI线程中,因此,onReceive()方法中不能执行太过于耗时的操作,否则可能给导致ANR异常。

    一般情况下,根据实际业务需求,onReceive()方法中都会涉及与其他组件之间的交互,如发送Notification、启动service等等。

    需要注意的是,一个BroadcastReceiver对象只有在被调用onReceive(context,Intent)时才有效,当该函数返回时,该对象就无效了,从而结束生命周期。

    因此,从这个特性来看,在所调用的onReceive(Context,Intent)函数里,不能有过于耗时的操作,不能使用线程来执行。对于耗时的操作,应该在startService中完成。因为当得到其他异步操作所返回的结果时,BroadcastReceiver可能已经无效了。

  • BroadcastReceiver的注册类型

    BroadcastReceiver总体上可以分为两种注册类型:静态注册和动态注册。

    1. 静态注册

      直接在AndroidManifest.xml文件中注册。

    2. 动态注册

      直接在代码中通过调用Context的registerReceiver函数,就可以在程序中动态注册BroadcastReceiver。

      动态注册的广播接收器可以自由的控制注册与注销,在灵活方面有很大的优势,但是它必须在程序启动之后才能接收广播,因为注册的逻辑是写在onCreat()中,所以没法在程序未启动的时候接收广播,这时候就需要使用静态广播,下面有示例。

    需要注意的是 ,Android中所有与观察者模式有关的设计中,一旦涉及register,必定在相应的时机需要用到unregister。一般在Activity的onStart()方法中进行注册,在onStop()方法中进行注销,如果在Activity.onResume()方法中注册,就必须在Activity.onPause()方法中注销。

  • BroadcastReceiver示例

    动态注册实现网络状态的监听:

    写一个类MyBroadcast继承至BroadcastReceiver:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class MyBroadcast extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
    ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo networkInfo = manager.getActiveNetworkInfo();
    if (networkInfo != null && networkInfo.isAvailable()) {
    Toast.makeText(context, "网络连接成功", Toast.LENGTH_SHORT).show();
    } else {
    Toast.makeText(context, "网络连接失败", Toast.LENGTH_SHORT).show();
    }
    }
    }

    然后写一个测试Aactivity:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class TestBroadcast extends Activity {
    private MyBroadcast mBroadcast;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_testbroadcast);
    mBroadcast= new MyBroadcast();
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
    registerReceiver(mBroadcast, intentFilter);
    }
    @Override
    protected void onDestroy() {
    super.onDestroy();
    unregisterReceiver(mBroadcast);
    }
    }

    采用动态注册,所以有注册就一定要有注销。我们想指定该广播接收器监听网络状态的变化,所以就添加对应的action。

    静态注册实现开机启动

    右键 new –> Other –> Broadcast Receiver 新建一个静态广播接收器。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class MyBootBroadcast extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
    // TODO: This method is called when the BroadcastReceiver is receiving
    // an Intent broadcast.
    Toast.makeText(context, "开机啦", Toast.LENGTH_SHORT).show();
    throw new UnsupportedOperationException("Not yet implemented");
    }
    }

    AS会自动在AndroidManifest中静态注册该广播。当然,我们要监听开机,所以还要添加Intent-filter所对应的Action。完善一下如下:

    1
    2
    3
    4
    5
    6
    7
    8
    <receiver
    android:name=".boradcast.MyBootBroadcast"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
    <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED"/>
    </intent-filter>
    </receiver>

    还有,别忘了赋予用户权限:

    1
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

有序广播

有序广播中的有序是针对广播接收者而言的,指的是发送出去的广播被BroadcastReceiver按照先后顺序接收。有序广播的定义过程与普通广播无异。只是其主要发送方式变为:sendOrderedBroadcast(Intent,receiverPermission,…)。

对于有序广播,其主要特点如下:

  1. 多个当前已经注册且有效的BroadcastReceiver接收有序广播时,是按照先后顺序接收的,先后顺序判定标准:将当前系统中所有有效的动态注册和静态注册的BroadcastReceiver按照Priority属性值从大到小排列,对于具有相同的Priority的动态广播和静态广播,动态广播会排在前面。
  2. 先接收的BroadcastReceiver可以对有序广播进行截断,使后面的BroadcastReceiver不在接收到此广播,也可以对广播进行修改,使后面的BroadcastReceiver接受到广播后解析到错误的参数值。当然,一般情况下,不建议对有序广播进行此类操作,尤其是系统中的有序广播。

接收系统广播消息

Android系统中内置了多个系统广播,只要涉及手机的基本操作,基本上都会发出相应的系统广播。如开机启动、网络状态改变、拍照、屏幕关闭与开启、电量不足等等。每个系统广播都具有特定的intent-filter,其中主要包括具体的action,系统广播发出后,将被相应的BroadcastReceiver接收。系统广播在系统内部有特定事件发生时,由系统自动发出。

最后

需要注意的是,广播在Android广播机制中并不是一种实体存在,而是以一种Intent来表示的,所以在广播的传输中我们可以让Intent携带数据传输给广播接收者。

关于广播更多的实例可以参考:

https://www.jianshu.com/p/ced5f62b7d25

我们一直都向往,面朝大海,春暖花开。 但是几人能做到,心中有爱,四季不败?